package com.koron.common.task.web.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.koron.common.core.util.CodeTools;
import com.koron.common.task.config.XxlJobConfig;
import com.koron.common.task.web.entity.*;
import com.koron.common.task.web.mapper.CronTaskMapper;
import com.koron.common.task.web.service.XxlJobService;
import com.koron.common.task.web.utils.CommonUtils;
import com.koron.common.task.web.utils.HttpClient;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import com.xxl.job.core.handler.impl.MethodJobHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.koron.ebs.mybatis.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * @author twcao
 * @title: XxlJobServiceImpl
 * @projectName GDH_EAM_V2
 * @description: job service实现
 * @date 2021/9/317:57
 */
@Slf4j
@Service
public class XxlJobServiceImpl implements XxlJobService {

    @Value("${spring.application.name}")
    public String JOB_GROUP_PREFIX;

    /**
     * 新增任务
     */
    public static final String JOB_ADD_URL = "/jobinfo/add";

    /**
     * 查询任务详情
     */
    public static final String JOB_DETAIL = "/jobinfo/getById";

    /**
     * 启动任务
     */
    public static final String JOB_START_URL = "/jobinfo/start";

    /**
     * 更新任务
     */
    public static final String JOB_UPDATE_URL = "/jobinfo/update";

    /**
     * 移除任务
     */
    public static final String JOB_DELETE_URL = "/jobinfo/remove";

    /**
     * 停止任务
     */
    public static final String JOB_STOP_URL = "/jobinfo/stop";

    /**
     * 查询所有的group
     */
    public static final String GROUP_FIND_ALL_URL = "/jobgroup/findAll";

    /**
     * 查询所有的group
     */
    public static final String JOB_TRIGGER_TIMES_RL = "/jobinfo/nextTriggerTime";

    @Autowired
    private XxlJobConfig xxlJobConfig;

    private HttpClient client = null;

    private List<XxlJobGroup> jobGroups;

    @PostConstruct
    public void initClient() {
        this.client = new HttpClient(xxlJobConfig.getAccessToken());
    }

    /**
     * 随机获取admin address
     * @return
     */
    private String getAdminUrl() {
        // 多个之间都好分隔
        String adminAddress = xxlJobConfig.getAdminAddresses();
        String[] adminUrlList = StringUtils.split(adminAddress, ",");
        Assert.notEmpty(adminUrlList, "admin address不能为空");
        return adminUrlList[(int) (adminUrlList.length * Math.random())];
    }

    /**
     * 获取后面几次的运行时间
     * @return
     */
    @Override
    public List<String> nextTriggerTime(String scheduleType, String scheduleConf, Integer time) {
        if(StringUtils.isBlank(scheduleType)) {
            scheduleType = "CRON";
        }
        String triggerTimes = client.doGet(getAdminUrl() + JOB_TRIGGER_TIMES_RL + "?scheduleType=" + scheduleType + "&scheduleConf=" + scheduleConf + "&triggerTimes=" + time);
        ReturnT returnT = JSON.parseObject(triggerTimes, ReturnT.class);
        Assert.isTrue(returnT != null && returnT.getCode() == 200, returnT.getMsg());
        List<String> triggerTime = (List) returnT.getContent();
        log.info("triggerTime = {}", JSON.toJSONString(triggerTime));
        return triggerTime;
    }

    /**
     * 获取后面几次的运行时间
     * @return
     */
    @Override
    public List<String> nextTriggerTime(String scheduleConf,  Integer triggerTime) {
        return nextTriggerTime(null, scheduleConf, triggerTime);
    }

    /**
     * 查询任务详情
     * @param xxlJobId
     * @return
     */
    @Override
    public XxlJobInfo getXxlJobInfoById(Integer xxlJobId) {
        try {
            String jobJson = client.doGet(getAdminUrl() + JOB_DETAIL + "?jobId=" + xxlJobId);
            log.info("xxlJobId = {}, 详情:{}", xxlJobId, jobJson);
            return JSON.parseObject(jobJson, XxlJobInfo.class);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 更新xxl-job
     */
    private boolean updateXxlJob(XxlJobInfo xxlJobInfo) {
        try {
            String jobJson = client.doPost(getAdminUrl() + JOB_UPDATE_URL, CommonUtils.objToMap(xxlJobInfo));
            ReturnT returnT = JSON.parseObject(jobJson, ReturnT.class);
            Assert.isTrue(returnT.getCode() == 200, "Xxl-Job更新失败");
            log.info("xxlJobInfo = {}, 更新成功", JSON.toJSONString(xxlJobInfo));
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 移除xxl-job
     * @param xxljobId
     */
    private boolean deleteXxlJob(int xxljobId) {
        try {
            String jobJson = client.doGet(getAdminUrl() + JOB_DELETE_URL + "?id=" + xxljobId);
            ReturnT returnT = JSON.parseObject(jobJson, ReturnT.class);
            Assert.isTrue(returnT.getCode() == 200, "Xxl-Job删除失败");
            log.info("xxljobId = {}, 删除成功", xxljobId);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 停止xxl-job任务
     * @param xxljobId
     */
    private boolean stopXxlJob(int xxljobId) {
        try {
            String jobJson = client.doGet(getAdminUrl() + JOB_STOP_URL + "?id=" + xxljobId);
            ReturnT returnT = JSON.parseObject(jobJson, ReturnT.class);
            Assert.isTrue(returnT.getCode() == 200, "Xxl-Job停止失败");
            log.info("xxljobId = {}, 停止成功", xxljobId);
            return true;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 随机获取job group
     * @return
     */
    private XxlJobGroup getJobGroup() {
        if(CollectionUtils.isEmpty(this.jobGroups)) {
            HashMap<String, String> params = new HashMap<>(4);
            params.put("appname", JOB_GROUP_PREFIX);
            try {
                String groupJson = client.doPost(getAdminUrl() + GROUP_FIND_ALL_URL, params);
                jobGroups = JSONArray.parseArray(groupJson, XxlJobGroup.class);
            } catch (Exception e) {
                jobGroups = null;
                log.error(e.getMessage(), e);
            }
        }
        Assert.notEmpty(jobGroups, "请配置执行器[job group]");
        return jobGroups.get((int) (jobGroups.size() * Math.random()));
    }

    /**
     * 根据业务id获取CronTask
     * @param bizId 业务id
     * @return
     */
    @Override
    public CronTask getCronTaskById(String bizId) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            return mapper.queryById(bizId);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 根据xxl-jobid查询任务
     *
     * @param xxlJobId
     * @return
     */
    @Override
    public CronTask getCronTaskByXxlJobId(Integer xxlJobId) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            return mapper.queryByXxlJobId(xxlJobId);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 新增任务
     * 新新建xxl-job，将然会的id存入s_cron_task
     * @param jobInfo
     * @return
     */
    @Override
    public Integer add(XxlJobInfo jobInfo, CronTask cronTask) {
        String jobJson = client.doPost(getAdminUrl() + JOB_ADD_URL, CommonUtils.objToMap(jobInfo));
        ReturnT returnT = JSON.parseObject(jobJson, ReturnT.class);
        Assert.isTrue(returnT.getCode() == 200, "定时任务创建失败");
        log.info("任务创建成功, Xxl-JobId = {}", returnT.getContent());
        Integer xxlJobId = Integer.parseInt(String.valueOf(returnT.getContent()));
        // 未启动前，为停止状态
        cronTask.setStatus(Constants.TASK_STOP);
        cronTask.setXxlJobId(xxlJobId);
        // 如果没有id， 则设置主键
        if(StringUtils.isBlank(cronTask.getId())) {
            cronTask.setId(CodeTools.getCode32());
        }
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper cronTaskMapper = factory.getMapper(CronTaskMapper.class);
            MethodJobHandler methodJobHandler = (MethodJobHandler) XxlJobSpringExecutor.loadJobHandler(jobInfo.getExecutorHandler());
            Field methodField = MethodJobHandler.class.getDeclaredField("method");
            methodField.setAccessible(true);
            Method method = (Method) methodField.get(methodJobHandler);
            cronTask.setMethod(method.getName());
            cronTask.setPath(method.getDeclaringClass().getCanonicalName());
            cronTaskMapper.saveOrUpdate(cronTask);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            log.info("数据库表[s_cron_task]入库失败， xxl-job任务删除");
            deleteXxlJob(cronTask.getXxlJobId());
            xxlJobId = null;
            throw new RuntimeException("定时任务创建失败");
        }
        return xxlJobId;
    }

    /**
     * 新增简单任务
     * @param bizId 业务id
     * @param cron cron表达式
     * @param mark 数据源标识
     * @param jobRemark job描述
     * @param author 任务归属人
     * @param xxlJobVal @XxlJob 注解的value值
     * @param timeOut 任务超时时间，单位秒
     * @return
     */
    @Override
    public Integer addSimpleJob(String bizId, String cron, String mark, String jobRemark, String author, String xxlJobVal, int timeOut) {
        CronTask cronTask = CommonUtils.parseCronToTask(cron);
        cronTask.setBusinessId(bizId);
        cronTask.setCron(cron);
        cronTask.setMark(mark);
        cronTask.setCronName(jobRemark);
        XxlJobInfo jobInfo = new XxlJobInfo();
        jobInfo.setJobDesc(jobRemark);
        jobInfo.setAuthor(author);
        jobInfo.setScheduleConf(cron);
        jobInfo.setExecutorHandler(xxlJobVal);
        XxlJobGroup jobGroup = getJobGroup();
        // 1、jobGroup 随机取值
        jobInfo.setJobGroup(jobGroup.getId());
        cronTask.setCronGroup(jobGroup.getAppname());
        // 2、调度类型为CRON
        jobInfo.setScheduleType("CRON");
        jobInfo.setGlueType("BEAN");
        // 集群方式下，路由策略设置位轮询
        jobInfo.setExecutorRouteStrategy("ROUND");
        // 3、过期策略
        jobInfo.setMisfireStrategy("DO_NOTHING");
        jobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        jobInfo.setExecutorTimeout(timeOut);
        jobInfo.setExecutorFailRetryCount(xxlJobConfig.getRetryTimes());
        return add(jobInfo, cronTask);
    }

    /**
     * 新增简单任务
     * @param bizId 业务id
     * @param cron cron表达式
     * @param mark 数据源标识
     * @param jobRemark job描述
     * @param author 任务归属人
     * @param xxlJobVal @XxlJob 注解的value值
     * @return
     */
    @Override
    public boolean update(String bizId, String cron, String mark, String jobRemark, String author, String xxlJobVal) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            CronTask cronTask = mapper.queryById(bizId);
            if(StringUtils.isNotBlank(cron)) {
                cronTask.setCron(cron);
            }
            if(StringUtils.isNotBlank(mark)) {
                cronTask.setMark(mark);
            }
            CronTask updateTask = CommonUtils.parseCronToTask(cron);
            cronTask.setYears(updateTask.getYears());
            cronTask.setMonths(updateTask.getMonths());
            cronTask.setWeeks(updateTask.getWeeks());
            cronTask.setDays(updateTask.getDays());
            cronTask.setHours(updateTask.getHours());
            cronTask.setMins(updateTask.getMins());
            mapper.saveOrUpdate(cronTask);
            XxlJobInfo jobInfo = getXxlJobInfoById(cronTask.getXxlJobId());
            Assert.notNull(jobInfo, "xxl-job任务不存在");
            if(StringUtils.isNotBlank(jobRemark)) {
                jobInfo.setJobDesc(jobRemark);
            }
            if(StringUtils.isNotBlank(author)) {
                jobInfo.setAuthor(author);
            }
            if(StringUtils.isNotBlank(cron)) {
                jobInfo.setScheduleConf(cron);
            }
            if(StringUtils.isNotBlank(xxlJobVal)) {
                jobInfo.setExecutorHandler(xxlJobVal);
            }
            updateXxlJob(jobInfo);
        }
        return true;
    }

    /**
     * 移除任务
     * @param bizId
     * @return
     */
    @Override
    public boolean remove(String bizId) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            CronTask cronTask = mapper.queryById(bizId);
            mapper.deleteById(bizId);
            if(cronTask != null && cronTask.getXxlJobId() != null) {
                deleteXxlJob(cronTask.getXxlJobId());
                // 删除可能存在的临时任务
                CronTask tmpCronTask = mapper.queryById(String.valueOf(cronTask.getXxlJobId()));
                if(!Objects.isNull(tmpCronTask)) {
                    mapper.deleteById(String.valueOf(cronTask.getXxlJobId()));
                    deleteXxlJob(tmpCronTask.getXxlJobId());
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    /**
     * 停止任务
     *
     * @param bizId
     * @return
     */
    @Override
    public boolean pause(String bizId) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            CronTask cronTask = mapper.queryById(bizId);
            cronTask.setStatus(Constants.TASK_STOP);
            mapper.saveOrUpdate(cronTask);
            stopXxlJob(cronTask.getXxlJobId());
        }
        return true;
    }

    /**
     * 开始任务
     * @param bizId
     * @return
     */
    @Override
    public boolean start(String bizId) {
        try (SessionFactory factory = new SessionFactory()) {
            CronTaskMapper mapper = factory.getMapper(CronTaskMapper.class);
            CronTask cronTask = mapper.queryById(bizId);
            cronTask.setStatus(Constants.TASK_RUNNING);
            mapper.saveOrUpdate(cronTask);
            String jobJson = client.doGet(getAdminUrl() + JOB_START_URL + "?id=" + cronTask.getXxlJobId());
            ReturnT returnT = JSON.parseObject(jobJson, ReturnT.class);
            Assert.isTrue(returnT.getCode() == 200, "定时任务启动失败");
            log.info("任务启动成功, Xxl-JobId = {}", cronTask.getXxlJobId());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return true;
    }
}
